package yipdf;

import java.awt.Color;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

/**
 * Quick and dirty Java Swing front-end for some pdfjam features...
 * Needs pdfjam in user PATH
 */
public class YiPDF extends javax.swing.JFrame implements IConsole {

    private static final String VERSION = "1.1";

    public YiPDF() {
        initComponents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        content = new javax.swing.JPanel();
        fileLabel = new javax.swing.JLabel();
        fileField = new javax.swing.JTextField();
        browseButton = new javax.swing.JButton();
        trimCheckBox = new javax.swing.JCheckBox();
        trimTextField = new javax.swing.JFormattedTextField();
        separator2 = new javax.swing.JSeparator();
        landscapeCheckBox = new javax.swing.JCheckBox();
        impose4upCheckBox = new javax.swing.JCheckBox();
        scaleCheckBox = new javax.swing.JCheckBox();
        scaleTextField = new javax.swing.JTextField();
        separator1 = new javax.swing.JSeparator();
        runButton = new javax.swing.JButton();
        closeButton = new javax.swing.JButton();
        outputScrollPane = new javax.swing.JScrollPane();
        outputTextArea = new javax.swing.JTextArea();
        separator3 = new javax.swing.JSeparator();
        noCleanupCheckBox = new javax.swing.JCheckBox();
        subsetCheckBox = new javax.swing.JCheckBox();
        subsetTextField = new javax.swing.JTextField();
        openResultCheckBox = new javax.swing.JCheckBox();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("YiPDF");
        setLocationByPlatform(true);

        content.setBorder(javax.swing.BorderFactory.createEmptyBorder(4, 4, 4, 4));
        content.setLayout(new java.awt.GridBagLayout());

        fileLabel.setText("PDF file");
        content.add(fileLabel, new java.awt.GridBagConstraints());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        content.add(fileField, gridBagConstraints);

        browseButton.setText("...");
        browseButton.setToolTipText("\"Browse file system\"");
        browseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                browseButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        content.add(browseButton, gridBagConstraints);

        trimCheckBox.setSelected(true);
        trimCheckBox.setText("Trim");
        trimCheckBox.setToolTipText("Trim input content (i.e. remove margins you find too big)");
        trimCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                trimCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        content.add(trimCheckBox, gridBagConstraints);

        trimTextField.setText("10mm 10mm 10mm 10mm");
        trimTextField.setToolTipText("Specify margins top clip: Left Bottom Right Top");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        content.add(trimTextField, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        content.add(separator2, gridBagConstraints);

        landscapeCheckBox.setSelected(true);
        landscapeCheckBox.setText("Landscape");
        landscapeCheckBox.setToolTipText("Input file is in landscape mode");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        content.add(landscapeCheckBox, gridBagConstraints);

        impose4upCheckBox.setSelected(true);
        impose4upCheckBox.setText("4up");
        impose4upCheckBox.setToolTipText("Transform the pdf so 4 pages fit into 1");
        impose4upCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                impose4upCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        content.add(impose4upCheckBox, gridBagConstraints);

        scaleCheckBox.setSelected(true);
        scaleCheckBox.setText("Scale");
        scaleCheckBox.setToolTipText("Scale final output (i.e. add margins to final document)");
        scaleCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                scaleCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        content.add(scaleCheckBox, gridBagConstraints);

        scaleTextField.setText("0.9");
        scaleTextField.setToolTipText("Scale value, enter any value from 0.0 to 1.0");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        content.add(scaleTextField, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        content.add(separator1, gridBagConstraints);

        runButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/yipdf/icon_go.png"))); // NOI18N
        runButton.setText("Run");
        runButton.setToolTipText("This will run commands to generate the desired PDF output document");
        runButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                runButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        content.add(runButton, gridBagConstraints);

        closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/yipdf/icon_cancel.png"))); // NOI18N
        closeButton.setText("Close");
        closeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 10;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        content.add(closeButton, gridBagConstraints);

        outputTextArea.setColumns(20);
        outputTextArea.setEditable(false);
        outputTextArea.setFont(new java.awt.Font("Monospaced", 0, 10)); // NOI18N
        outputTextArea.setRows(5);
        outputScrollPane.setViewportView(outputTextArea);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weighty = 1.0;
        content.add(outputScrollPane, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        content.add(separator3, gridBagConstraints);

        noCleanupCheckBox.setText("Keep temporary files");
        noCleanupCheckBox.setToolTipText("Check this if you don't want to see intermediate files");
        noCleanupCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                noCleanupCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 7;
        content.add(noCleanupCheckBox, gridBagConstraints);

        subsetCheckBox.setText("Subset");
        subsetCheckBox.setToolTipText("Do not use entire document but a subset (pages extraction)");
        subsetCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                subsetCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        content.add(subsetCheckBox, gridBagConstraints);

        subsetTextField.setToolTipText("<html>Specify here which pages to extract. Syntax examples:<br><b>3,15,18</b> (list)<br><b>7-30</b> (interval)<br><b>-12</b> (from beginning to 12 included)<br><b>42-</b> (from 42 included to end)<br><b>4,9,12-21,34-</b>(more complexe combination)</html>");
        subsetTextField.setEnabled(false);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        content.add(subsetTextField, gridBagConstraints);

        openResultCheckBox.setSelected(true);
        openResultCheckBox.setText("Open output file");
        openResultCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openResultCheckBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 7;
        content.add(openResultCheckBox, gridBagConstraints);

        getContentPane().add(content, java.awt.BorderLayout.CENTER);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void scaleCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_scaleCheckBoxActionPerformed
        scaleTextField.setEnabled(scaleCheckBox.isSelected());
    }//GEN-LAST:event_scaleCheckBoxActionPerformed

    private void trimCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_trimCheckBoxActionPerformed
        trimTextField.setEnabled(trimCheckBox.isSelected());
    }//GEN-LAST:event_trimCheckBoxActionPerformed

    private void impose4upCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_impose4upCheckBoxActionPerformed
        scaleCheckBox.setEnabled(impose4upCheckBox.isSelected());
        scaleCheckBox.setSelected(impose4upCheckBox.isSelected());
        scaleTextField.setEnabled(impose4upCheckBox.isSelected());
    }//GEN-LAST:event_impose4upCheckBoxActionPerformed

    private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new FileNameExtensionFilter("PDF documents", "pdf"));
        int returnVal = chooser.showOpenDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            fileField.setText(chooser.getSelectedFile().getAbsolutePath());
        }
    }//GEN-LAST:event_browseButtonActionPerformed

    private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
        //dispose();
        System.exit(0);// just in case background thread is stuck because of weird parameters...
    }//GEN-LAST:event_closeButtonActionPerformed

    private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runButtonActionPerformed
        runButton.setEnabled(false);
        closeButton.setEnabled(false);
        if (taskResult_ != null && !taskResult_.isDone()) {
            JOptionPane.showMessageDialog(this, "Still working...", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }
        final Runnable reenableButtons = new Runnable() {

            public void run() {
                runButton.setEnabled(true);
                closeButton.setEnabled(true);
            }
        };
        Callable<File> work = new Callable<File>() {

            public File call() throws Exception {
                File result = gatherParametersAndRunCommand();
                if (result != null && openResultCheckBox.isSelected() && Desktop.isDesktopSupported()) {
                    Desktop.getDesktop().open(result);
                }
                SwingUtilities.invokeLater(reenableButtons);
                return result;
            }
        };
        taskResult_ = execService_.submit(work);
    }//GEN-LAST:event_runButtonActionPerformed

    private void subsetCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_subsetCheckBoxActionPerformed
        subsetTextField.setEnabled(subsetCheckBox.isSelected());
    }//GEN-LAST:event_subsetCheckBoxActionPerformed

    private void openResultCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openResultCheckBoxActionPerformed
        // TODO add your handling code here:
    }//GEN-LAST:event_openResultCheckBoxActionPerformed

    private void noCleanupCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noCleanupCheckBoxActionPerformed
        // TODO add your handling code here:
    }//GEN-LAST:event_noCleanupCheckBoxActionPerformed

    private File gatherParametersAndRunCommand() throws IOException {
        String path = fileField.getText();
        if (path == null) {
            throw new IOException("Please choose a file first");
        }
        File file = new File(path);
        if (!file.exists()) {
            throw new IOException("File does not exist: " + file);
        }
        if (!file.canRead()) {
            throw new IOException("Permission error, cannot read file " + file);
        }
        if (!file.getParentFile().canWrite()) {
            throw new IOException("Permission error, cannot write to parent directory " + file.getParent());
        }
        float scale = -1.0F;
        try {
            scale = Float.valueOf(scaleTextField.getText());
        } catch (NumberFormatException e) {
            throw new IOException("Error: invalid scale (" + e.getMessage() + ")");
        }
        if (scale >= 1.0F) {
            scale = -1.0F;
        }
        return pdfjam_.runPdfJam(
                file,
                landscapeCheckBox.isSelected(),
                getTextFieldInput(subsetTextField, subsetCheckBox),
                getTextFieldInput(trimTextField, trimCheckBox),
                impose4upCheckBox.isSelected(),
                scale,
                !noCleanupCheckBox.isSelected());
    }

    private String getTextFieldInput(JTextField field, JCheckBox check) {
        if (!check.isSelected()) {
            return null;
        }
        String input = field.getText();
        return input == null ? null : (input.trim().length() == 0 ? null : input);
    }

    public void printError(String s) {
        print(s, OUTPUT_ERROR_STYLE);
    }

    public void printSpecial(String s) {
        print(s, OUTPUT_SPECIAL_STYLE);
    }

    public void print(final String s) {
        print(s, OUTPUT_STYLE);
    }

    private void print(final String s, final AttributeSet attr) {
        Runnable task = new Runnable() {

            public void run() {
                Document doc = outputTextArea.getDocument();
                try {
                    doc.insertString(doc.getLength(), s + "\n", attr);
                } catch (BadLocationException ex) {
                    Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, "Failed to append string " + s, ex);
                }
            }
        };
        if (SwingUtilities.isEventDispatchThread()) {
            task.run();
        } else {
            SwingUtilities.invokeLater(task);
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                ToolTipManager.sharedInstance().setDismissDelay(10000);
                YiPDF frame = new YiPDF();
                frame.setTitle("YiPDF - " + VERSION);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton browseButton;
    private javax.swing.JButton closeButton;
    private javax.swing.JPanel content;
    private javax.swing.JTextField fileField;
    private javax.swing.JLabel fileLabel;
    private javax.swing.JCheckBox impose4upCheckBox;
    private javax.swing.JCheckBox landscapeCheckBox;
    private javax.swing.JCheckBox noCleanupCheckBox;
    private javax.swing.JCheckBox openResultCheckBox;
    private javax.swing.JScrollPane outputScrollPane;
    private javax.swing.JTextArea outputTextArea;
    private javax.swing.JButton runButton;
    private javax.swing.JCheckBox scaleCheckBox;
    private javax.swing.JTextField scaleTextField;
    private javax.swing.JSeparator separator1;
    private javax.swing.JSeparator separator2;
    private javax.swing.JSeparator separator3;
    private javax.swing.JCheckBox subsetCheckBox;
    private javax.swing.JTextField subsetTextField;
    private javax.swing.JCheckBox trimCheckBox;
    private javax.swing.JFormattedTextField trimTextField;
    // End of variables declaration//GEN-END:variables
    private PDFJamWrapper pdfjam_ = new PDFJamWrapper(this);
    private ExecutorService execService_ = Executors.newSingleThreadExecutor();
    private Future<File> taskResult_;
    private static SimpleAttributeSet OUTPUT_STYLE = new SimpleAttributeSet();
    private static SimpleAttributeSet OUTPUT_ERROR_STYLE = new SimpleAttributeSet();
    private static SimpleAttributeSet OUTPUT_SPECIAL_STYLE = new SimpleAttributeSet();

    static {
        StyleConstants.setBold(OUTPUT_ERROR_STYLE, true);
        StyleConstants.setForeground(OUTPUT_ERROR_STYLE, Color.RED);
        StyleConstants.setFontFamily(OUTPUT_STYLE, "Monospaced");
        StyleConstants.setFontFamily(OUTPUT_ERROR_STYLE, "Monospaced");
        StyleConstants.setItalic(OUTPUT_SPECIAL_STYLE, true);
        StyleConstants.setForeground(OUTPUT_SPECIAL_STYLE, Color.GRAY);
        StyleConstants.setFontFamily(OUTPUT_SPECIAL_STYLE, "Monospaced");
    }
}
